/** 
    @file input_manager.cpp
    @brief Input Manager Class functions
*/

#include "input/input_manager.hpp"

pm3::INPUT_MANAGER *pm3::INPUT_MANAGER::m_input_manager;

pm3::INPUT_MANAGER::INPUT_MANAGER( void ) :
    m_mouse( 0 ),
    m_keyboard( 0 ),
    m_input_system( 0 ) {

    m_limit_mouse = true;
}

pm3::INPUT_MANAGER::~INPUT_MANAGER( void ) {
    cleanup();
}

void pm3::INPUT_MANAGER::initialise( Ogre::RenderWindow *p_render_window ) {
    if( !m_input_system ) {
        // Setup basic variables
        OIS::ParamList paramList;    
        size_t windowHnd = 0;
        std::ostringstream windowHndStr;

        // Get window handle
        p_render_window->getCustomAttribute( "WINDOW", &windowHnd );

        // Fill parameter list
        windowHndStr << (unsigned int) windowHnd;
        paramList.insert( std::make_pair( std::string( "WINDOW" ), windowHndStr.str() ) );

        // Create inputsystem
        m_input_system = OIS::InputManager::createInputSystem( paramList );

        // If possible create a buffered keyboard
        // (note: if below line doesn't compile, try:  if (m_input_system->getNumberOfDevices(OIS::OISKeyboard) > 0) {
        //if( m_input_system->num_keyboards() > 0 ) {
        if (m_input_system->getNumberOfDevices(OIS::OISKeyboard) > 0) {
            m_keyboard = static_cast<OIS::Keyboard*>( m_input_system->createInputObject( OIS::OISKeyboard, true ) );
            m_keyboard->setEventCallback( this );
        }

        // If possible create a buffered mouse
        // (note: if below line doesn't compile, try:  if (m_input_system->getNumberOfDevices(OIS::OISMouse) > 0) {
        //if( m_input_system->numMice() > 0 ) {
        if (m_input_system->getNumberOfDevices(OIS::OISMouse) > 0) {
            m_mouse = static_cast<OIS::Mouse*>( m_input_system->createInputObject( OIS::OISMouse, true ) );
            m_mouse->setEventCallback( this );

            // Get window size
            unsigned int width, height, depth;
            int left, top;
            p_render_window->getMetrics( width, height, depth, left, top );

            // Set mouse region
            this->set_window_extents( width, height );
        }

        // If possible create all joysticks in buffered mode
        // (note: if below line doesn't compile, try:  if (m_input_system->getNumberOfDevices(OIS::OISJoyStick) > 0) {
        //if( m_input_system->num_joysticks() > 0 ) {
        if (m_input_system->getNumberOfDevices(OIS::OISJoyStick) > 0) {
            //m_joysticks.resize( m_input_system->num_joysticks() );
            m_joysticks.resize( m_input_system->getNumberOfDevices(OIS::OISJoyStick) );

            it_joystick    = m_joysticks.begin();
            it_joystick_end = m_joysticks.end();
            for(; it_joystick != it_joystick_end; ++it_joystick ) {
                (*it_joystick) = static_cast<OIS::JoyStick*>( m_input_system->createInputObject( OIS::OISJoyStick, true ) );
                (*it_joystick)->setEventCallback( this );
            }
        }
    }
}

void pm3::INPUT_MANAGER::cleanup( void ) {
    if( m_input_system ) {
        if( m_mouse ) {
            m_input_system->destroyInputObject( m_mouse );
            m_mouse = 0;
        }

        if( m_keyboard ) {
            m_input_system->destroyInputObject( m_keyboard );
            m_keyboard = 0;
        }

        if( m_joysticks.size() > 0 ) {
            it_joystick    = m_joysticks.begin();
            it_joystick_end = m_joysticks.end();
            for(; it_joystick != it_joystick_end; ++it_joystick ) {
                m_input_system->destroyInputObject( *it_joystick );
            }

            m_joysticks.clear();
        }

        // If you use OIS1.0RC1 or above, uncomment this line
        // and comment the line below it
        m_input_system->destroyInputSystem( m_input_system );
        //m_input_system->destroyInputSystem();
        m_input_system = 0;

        // Clear Listeners
        m_key_listeners.clear();
        m_mouse_listeners.clear();
        m_joystick_listeners.clear();
    }
}

void pm3::INPUT_MANAGER::capture( void ) {
    // Need to capture / update each device every frame
    if( m_mouse ) {
        m_mouse->capture();
    }

    if( m_keyboard ) {
        m_keyboard->capture();
    }

    if( m_joysticks.size() > 0 ) {
        it_joystick    = m_joysticks.begin();
        it_joystick_end = m_joysticks.end();
        for(; it_joystick != it_joystick_end; ++it_joystick ) {
            (*it_joystick)->capture();
        }
    }
}

void pm3::INPUT_MANAGER::add_key_listener( OIS::KeyListener *p_key_listener, const std::string &r_instance_name ) {
    if( m_keyboard ) {
        // Check for duplicate items
        it_key_listener = m_key_listeners.find( r_instance_name );
        if( it_key_listener == m_key_listeners.end() ) {
            m_key_listeners[ r_instance_name ] = p_key_listener;
        }
        else {
            // Duplicate Item
        }
    }
}

void pm3::INPUT_MANAGER::add_mouse_listener( OIS::MouseListener *p_mouse_listener, const std::string &r_instance_name ) {
    if( m_mouse ) {
        // Check for duplicate items
        it_mouse_listener = m_mouse_listeners.find( r_instance_name );
        if( it_mouse_listener == m_mouse_listeners.end() ) {
            m_mouse_listeners[ r_instance_name ] = p_mouse_listener;
        }
        else {
            // Duplicate Item
        }
    }
}

void pm3::INPUT_MANAGER::add_joystick_listener( OIS::JoyStickListener *p_joystick_listener, const std::string &r_instance_name ) {
    if( m_joysticks.size() > 0 ) {
        // Check for duplicate items
        it_joystick_listener = m_joystick_listeners.find( r_instance_name );
        if( it_joystick_listener == m_joystick_listeners.end() ) {
            m_joystick_listeners[ r_instance_name ] = p_joystick_listener;
        }
        else {
            // Duplicate Item
        }
    }
}

void pm3::INPUT_MANAGER::remove_key_listener( const std::string& r_instance_name ) {
    // Check if item exists
    it_key_listener = m_key_listeners.find( r_instance_name );
    if( it_key_listener != m_key_listeners.end() ) {
        m_key_listeners.erase( it_key_listener );
    }
    else {
        // Doesn't Exist
    }
}

void pm3::INPUT_MANAGER::remove_mouse_listener( const std::string& r_instance_name ) {
    // Check if item exists
    it_mouse_listener = m_mouse_listeners.find( r_instance_name );
    if( it_mouse_listener != m_mouse_listeners.end() ) {
        m_mouse_listeners.erase( it_mouse_listener );
    }
    else {
        // Doesn't Exist
    }
}

void pm3::INPUT_MANAGER::remove_joystick_listener( const std::string& r_instance_name ) {
    // Check if item exists
    it_joystick_listener = m_joystick_listeners.find( r_instance_name );
    if( it_joystick_listener != m_joystick_listeners.end() ) {
        m_joystick_listeners.erase( it_joystick_listener );
    }
    else {
        // Doesn't Exist
    }
}

void pm3::INPUT_MANAGER::remove_key_listener( OIS::KeyListener *p_key_listener ) {
    it_key_listener    = m_key_listeners.begin();
    it_key_listener_end = m_key_listeners.end();
    for(; it_key_listener != it_key_listener_end; ++it_key_listener ) {
        if( it_key_listener->second == p_key_listener ) {
            m_key_listeners.erase( it_key_listener );
            break;
        }
    }
}

void pm3::INPUT_MANAGER::remove_mouse_listener( OIS::MouseListener *p_mouse_listener ) {
    it_mouse_listener    = m_mouse_listeners.begin();
    it_mouse_listener_end = m_mouse_listeners.end();
    for(; it_mouse_listener != it_mouse_listener_end; ++it_mouse_listener ) {
        if( it_mouse_listener->second == p_mouse_listener ) {
            m_mouse_listeners.erase( it_mouse_listener );
            break;
        }
    }
}

void pm3::INPUT_MANAGER::remove_joystick_listener( OIS::JoyStickListener *p_joystick_listener ) {
    it_joystick_listener    = m_joystick_listeners.begin();
    it_joystick_listener_end = m_joystick_listeners.end();
    for(; it_joystick_listener != it_joystick_listener_end; ++it_joystick_listener ) {
        if( it_joystick_listener->second == p_joystick_listener ) {
            m_joystick_listeners.erase( it_joystick_listener );
            break;
        }
    }
}

void pm3::INPUT_MANAGER::remove_all_listeners( void ) {
    m_key_listeners.clear();
    m_mouse_listeners.clear();
    m_joystick_listeners.clear();
}

void pm3::INPUT_MANAGER::remove_all_key_listeners( void ) {
    m_key_listeners.clear();
}

void pm3::INPUT_MANAGER::remove_all_mouse_listeners( void ) {
    m_mouse_listeners.clear();
}

void pm3::INPUT_MANAGER::remove_all_joystick_listeners( void ) {
    m_joystick_listeners.clear();
}

void pm3::INPUT_MANAGER::set_window_extents( int width, int height ) {
    if (!m_limit_mouse)
        return;
    // Set mouse region (if window resizes, we should alter this to reflect as well)
    const OIS::MouseState &mouseState = m_mouse->getMouseState();
    mouseState.width  = width;
    mouseState.height = height;
}

OIS::Mouse* pm3::INPUT_MANAGER::get_mouse( void ) {
    return m_mouse;
}

OIS::Keyboard* pm3::INPUT_MANAGER::get_keyboard( void ) {
    return m_keyboard;
}

OIS::JoyStick* pm3::INPUT_MANAGER::get_joystick( unsigned int i_index ) {
    // Make sure it's a valid index
    if( i_index < m_joysticks.size() ) {
        return m_joysticks[ i_index ];
    }

    return 0;
}

int pm3::INPUT_MANAGER::get_num_joysticks( void ) {
    // Cast to keep compiler happy ^^
    return (int) m_joysticks.size();
}

bool pm3::INPUT_MANAGER::keyPressed( const OIS::KeyEvent &r_event ) {
    it_key_listener    = m_key_listeners.begin();
    it_key_listener_end = m_key_listeners.end();
    for(; it_key_listener != it_key_listener_end; ++it_key_listener ) {
        if(!it_key_listener->second->keyPressed( r_event ))
			break;
    }

    return true;
}

bool pm3::INPUT_MANAGER::keyReleased( const OIS::KeyEvent &r_event ) {
    it_key_listener    = m_key_listeners.begin();
    it_key_listener_end = m_key_listeners.end();
    for(; it_key_listener != it_key_listener_end; ++it_key_listener ) {
        if(!it_key_listener->second->keyReleased( r_event ))
			break;
    }

    return true;
}

bool pm3::INPUT_MANAGER::mouseMoved( const OIS::MouseEvent &r_event ) {
    it_mouse_listener    = m_mouse_listeners.begin();
    it_mouse_listener_end = m_mouse_listeners.end();
    for(; it_mouse_listener != it_mouse_listener_end; ++it_mouse_listener ) {
        if(!it_mouse_listener->second->mouseMoved( r_event ))
			break;
    }

    return true;
}

bool pm3::INPUT_MANAGER::mousePressed( const OIS::MouseEvent &r_event, OIS::MouseButtonID i_id ) {
    it_mouse_listener    = m_mouse_listeners.begin();
    it_mouse_listener_end = m_mouse_listeners.end();
    for(; it_mouse_listener != it_mouse_listener_end; ++it_mouse_listener ) {
        if(!it_mouse_listener->second->mousePressed( r_event, i_id ))
			break;
    }

    return true;
}

bool pm3::INPUT_MANAGER::mouseReleased( const OIS::MouseEvent &r_event, OIS::MouseButtonID i_id ) {
    it_mouse_listener    = m_mouse_listeners.begin();
    it_mouse_listener_end = m_mouse_listeners.end();
    for(; it_mouse_listener != it_mouse_listener_end; ++it_mouse_listener ) {
        if(!it_mouse_listener->second->mouseReleased( r_event, i_id ))
			break;
    }

    return true;
}

bool pm3::INPUT_MANAGER::povMoved( const OIS::JoyStickEvent &r_event, int i_pov ) {
    it_joystick_listener    = m_joystick_listeners.begin();
    it_joystick_listener_end = m_joystick_listeners.end();
    for(; it_joystick_listener != it_joystick_listener_end; ++it_joystick_listener ) {
        if(!it_joystick_listener->second->povMoved( r_event, i_pov ))
			break;
    }

    return true;
}

bool pm3::INPUT_MANAGER::axisMoved( const OIS::JoyStickEvent &r_event, int i_axis ) {
    it_joystick_listener    = m_joystick_listeners.begin();
    it_joystick_listener_end = m_joystick_listeners.end();
    for(; it_joystick_listener != it_joystick_listener_end; ++it_joystick_listener ) {
        if(!it_joystick_listener->second->axisMoved( r_event, i_axis ))
			break;
    }

    return true;
}

bool pm3::INPUT_MANAGER::sliderMoved( const OIS::JoyStickEvent &r_event, int i_slider_id ) {
    it_joystick_listener    = m_joystick_listeners.begin();
    it_joystick_listener_end = m_joystick_listeners.end();
    for(; it_joystick_listener != it_joystick_listener_end; ++it_joystick_listener ) {
        if(!it_joystick_listener->second->sliderMoved( r_event, i_slider_id ))
			break;
    }

    return true;
}

bool pm3::INPUT_MANAGER::buttonPressed( const OIS::JoyStickEvent &r_event, int i_button ) {
    it_joystick_listener    = m_joystick_listeners.begin();
    it_joystick_listener_end = m_joystick_listeners.end();
    for(; it_joystick_listener != it_joystick_listener_end; ++it_joystick_listener ) {
        if(!it_joystick_listener->second->buttonPressed( r_event, i_button ))
			break;
    }

    return true;
}

bool pm3::INPUT_MANAGER::buttonReleased( const OIS::JoyStickEvent &r_event, int i_button ) {
    it_joystick_listener    = m_joystick_listeners.begin();
    it_joystick_listener_end = m_joystick_listeners.end();
    for(; it_joystick_listener != it_joystick_listener_end; ++it_joystick_listener ) {
        if(!it_joystick_listener->second->buttonReleased( r_event, i_button ))
			break;
    }

    return true;
}

pm3::INPUT_MANAGER* pm3::INPUT_MANAGER::get_singleton_ptr( void ) {
    if( !m_input_manager ) {
        m_input_manager = new INPUT_MANAGER();
    }

    return m_input_manager;
}

